/**
 * Copyright (c) 2022 Presto Labs Pte. Ltd.
 * Author: Ju
 */

#pragma once

#include <map>
#include <string>
#include <unordered_map>

#include <boost/functional/hash.hpp>

#include "coin2/base/request/session.h"

namespace coin2::base::request {

static constexpr int32_t rtt_ms[4] = {200, 500, 1000, 5000};

// A request from an exchange
// key{method,endpoint}
using RequestKey = std::pair<std::string, std::string>;

// value
struct RequestInfo {
  RequestInfo() {}
  ~RequestInfo() = default;
  // first{http_code} second{count}
  std::unordered_map<int32_t, uint32_t> http_code;

  RequestInfo(const RequestInfo& rhs) { http_code = rhs.http_code; }
  RequestInfo(RequestInfo&& rhs) {
    http_code = std::move(rhs.http_code);
    rhs.http_code.clear();
  }
  RequestInfo& operator+=(const RequestInfo& rhs) {
    for (auto& x : rhs.http_code) {
      http_code[x.first] += x.second;
    }
    return *this;
  }
  RequestInfo& operator=(const RequestInfo& rhs) {
    http_code = rhs.http_code;
    return *this;
  }
  RequestInfo& operator=(RequestInfo&& rhs) {
    http_code = std::move(rhs.http_code);
    rhs.http_code.clear();
    return *this;
  }

  void AddInfo(int32_t code) { ++http_code[code]; }

  void Reset() { http_code.clear(); }
};
using RequestStatus = std::unordered_map<RequestKey, RequestInfo, boost::hash<RequestKey>>;

struct RttInfo {
  RttInfo() { rtt_point.resize(5, 0); }
  std::vector<int32_t> rtt_point;
  int32_t req_num = 0;
  int32_t rep_num = 0;
  int32_t err_num = 0;
  std::vector<int32_t> work_session_size;
  std::vector<int32_t> free_session_size;

  void ReportSessionSize(int32_t work, int32_t free) {
    work_session_size.emplace_back(work);
    free_session_size.emplace_back(free);
  }

  void Add(RttInfo t) {
    for (int i = 0; i < 5; i++) {
      rtt_point[i] += t.rtt_point[i];
    }
    req_num += t.req_num;
    rep_num += t.rep_num;
    err_num += t.err_num;
    work_session_size.insert(
        work_session_size.end(),
        t.work_session_size.begin(),
        t.work_session_size.end());
    free_session_size.insert(
        free_session_size.end(),
        t.free_session_size.begin(),
        t.free_session_size.end());
  }
  void Reset() {
    for (int i = 0; i < 5; i++) {
      rtt_point[i] = 0;
    }
    req_num = 0;
    rep_num = 0;
    err_num = 0;
    work_session_size.clear();
    free_session_size.clear();
  }
  void ReqNum() { ++req_num; }
  void ErrNum() { ++err_num; }
  void Calculate(int32_t rtt) {
    ++rep_num;
    for (int i = 0; i < 4; i++) {
      if (rtt < rtt_ms[i]) {
        ++rtt_point[i];
        return;
      }
    }
    // other
    ++rtt_point[4];
  }
};

class ISessionPool {
 public:
  virtual ~ISessionPool() = default;
  virtual void StartTimer() = 0;
  virtual void StopTimer() = 0;
  virtual void SetTimeout(int64_t timeout_ns) = 0;
  virtual bool is_blocking() const = 0;
  virtual void AsyncRequest(
      const std::string& host,
      int port,
      const std::string& bind_ip,
      Session::RequestType req,
      Session::CallbackType callback,
      RequestPriorityWeight priority_weight) = 0;
  virtual void SetHttpProxy(const std::string& http_proxy) = 0;
  virtual RttInfo GetRttInfo() = 0;
  virtual RequestStatus GetRequestInfo() { return RequestStatus{}; };
};
}  // namespace coin2::base::request
